﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.ServiceModel;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Serialization;
using Ensynch;
using Ensynch.Collections.Generic;
using Ensynch.FIM;
using Ensynch.Logging;
using log4net.Core;
using Microsoft.ResourceManagement.Client;
using Microsoft.ResourceManagement.ObjectModel;

namespace FIMQueryTool
{
    public delegate void AsyncVoidMethod();
    public delegate void AsyncVoidMethodBool(bool arg0);

    public enum QueryState
    {
        STOPPED,
        RUNNING,
        PAUSED
    }

    public partial class MainDialog : Form
    {
        // Create a logger for use in this class.
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(
                System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        private static readonly RichTextBoxAppender richTextBoxAppender = new RichTextBoxAppender();

        public const string REF_GUID = "GUID";
        public const string REF_GUID_DISPLAY_NAME = "GUID (DisplayName)";
        public const string REF_DISPLAY_NAME_GUID = "DisplayName (GUID)";
        public const string REF_DISPLAY_NAME = "DisplayName";

        private Thread queryThread;
        private Thread objectThread;
        private Thread attributeThread;
        RecentList recent;
        private Dictionary<string, string> dereferenceDictionary = new Dictionary<string, string>();
        private Dictionary<string, string> currentAttributeSelection =
            new Dictionary<string, string>();

        private FimTools tools;

        public MainDialog()
        {
            InitializeComponent();

            RichTextBoxAppender.SetRichTextBox(this.outputTextBox, "outputTextBoxAppender");

            tabContainer.ImageList = new ImageList();
            tabContainer.ImageList.Images.Add(global::FIMQueryTool.Properties.Resources.error);

            Dictionary<string, string> listItems = new Dictionary<string, string>();
            listItems.Add(FimTools.FORMAT_GUID, REF_GUID);
            listItems.Add(FimTools.FORMAT_DISPLAY_NAME, REF_DISPLAY_NAME);
            //listItems.Add(FimTools.FORMAT_GUID_DISPLAY_NAME, REF_GUID_DISPLAY_NAME);
            //listItems.Add(FimTools.FORMAT_DISPLAY_NAME_GUID, REF_DISPLAY_NAME_GUID);

            referenceFormatComboBox.DataSource = new BindingSource(listItems, null);
            referenceFormatComboBox.DisplayMember = "Value";
            referenceFormatComboBox.ValueMember = "Key";
            this.referenceFormatComboBox.SelectedIndexChanged += new System.EventHandler(this.referenceFormatComboBox_SelectedIndexChanged);

            recent = RecentList.TryGetFromRegistry(assemblyRegKey,
                "Recent", 15);
            recent.ListChanged += new EventHandler(recent_ListChanged);
            recent_ListChanged(recent, new EventArgs());

            initializeAsync();
        }

        private string _assemblyRegKey;
        private string assemblyRegKey
        {
            get
            {
                if (_assemblyRegKey == null)
                {
                    _assemblyRegKey = 
                        string.Format(@"HKEY_CURRENT_USER\SOFTWARE\{0}\{1}",
                        Assembly.GetExecutingAssembly().GetName().Name,
                        Assembly.GetExecutingAssembly().GetName().Version);
                }
                return _assemblyRegKey;
            }
        }

        private string _currentFilePath = null;
        private string currentFilePath
        {
            get
            {
                return _currentFilePath;
            }
            set
            {
                _currentFilePath = value;
                if (_currentFilePath != null)
                {
                    toolStripStatusLabel1.Text = _currentFilePath;
                    FileInfo fi = new FileInfo(_currentFilePath);
                    recent.Push(fi.Name, fi.FullName);
                }
                else
                {
                    toolStripStatusLabel1.Text = "No open file";
                }
            }
        }

        private QueryState theQueryState = QueryState.STOPPED;
        public QueryState TheQueryState {
            get
            {
                return theQueryState;
            }
            set
            {
                theQueryState = value;
                // 'Invoke' is necessary to access cross-thread controls.
                Invoke(new MethodInvoker(
                    delegate
                    {
                        runQueryButton.Visible = theQueryState == QueryState.STOPPED;
                        stopQueryButton.Visible = theQueryState != QueryState.STOPPED;
                        pauseQueryButton.Visible = theQueryState == QueryState.RUNNING;
                        resumeQueryButton.Visible = theQueryState == QueryState.PAUSED;
                        runQueryImage.Visible = theQueryState == QueryState.RUNNING;
                    }));
            }
        }

        private void initializeAsync()
        {
            // Create the delegate.
            AsyncVoidMethod caller = new AsyncVoidMethod(initialize);
            // Initiate the asychronous call.
            IAsyncResult result = caller.BeginInvoke(null, null);
        }

        private void initialize()
        {
            try
            {
                Invoke(new MethodInvoker(
                    delegate
                    {
                        objectReloadButton.Visible = false;
                        objectLoadImage.Visible = true;
                    }));
                tools = new FimTools(new DefaultClient(), true);
            }
            catch (Exception exc)
            {
                log.Error("[initialize]", exc);
                showErrorIcon();
                focusMessageTab();
            }
            finally
            {
                Invoke(new MethodInvoker(
                    delegate
                    {
                        objectLoadImage.Visible = false;
                        objectReloadButton.Visible = true;
                    }));
            }

            loadObjectComboBox(false);
        }

        private void loadObjectComboBoxAsync()
        {
            //// Create the delegate.
            //AsyncVoidMethodBool caller = new AsyncVoidMethodBool(loadObjectComboBox);
            //// Initiate the asychronous call.
            //objectThread = caller.BeginInvoke(false, null, null);
            objectThread = new Thread(new ThreadStart(loadObjectComboBox));
            objectThread.Start();
        }

        private void reloadObjectComboBoxAsync(object sender, EventArgs e)
        {
            //// Create the delegate.
            //AsyncVoidMethodBool caller = new AsyncVoidMethodBool(loadObjectComboBox);
            //// Initiate the asychronous call.
            //objectThread = caller.BeginInvoke(true, null, null);
            objectThread = new Thread(new ThreadStart(reloadObjectComboBox));
            objectThread.Start();
        }

        private void loadObjectComboBox()
        {
            loadObjectComboBox(false);
        }

        private void reloadObjectComboBox()
        {
            loadObjectComboBox(true);
        }

        // The method to be executed asynchronously.
        private void loadObjectComboBox(bool refresh)
        {
            try
            {
                if (InvokeRequired)
                {
                    // 'Invoke' is necessary to access cross-thread controls.
                    Invoke(new MethodInvoker(
                        delegate
                        {
                            loadObjectComboBox_step1();
                        }));
                }
                else
                {
                    loadObjectComboBox_step1();
                }

                // Do not include this block in the Invoke call!
                if (refresh)
                {
                    tools.RefreshObjectDictionary();
                }
                var objList = EnsynchTools.SortDictionaryByValue(tools.ObjectDictionary);

                if (InvokeRequired)
                {
                    Invoke(new MethodInvoker(
                        delegate
                        {
                            loadObjectComboBox_step2(objList);
                        }));
                }
                else
                {
                    loadObjectComboBox_step2(objList);
                }

                loadAttributeComboBoxAsync(null, null);
            }
            catch (Exception exc)
            {
                log.Error("[loadObjectComboBox]", exc);
                showErrorIcon();
            }
            finally
            {
                if (InvokeRequired)
                {
                    Invoke(new MethodInvoker(
                        delegate
                        {
                            loadObjectComboBox_step3();
                        }));
                }
                else
                {
                    loadObjectComboBox_step3();
                }
            }
        }

        private void loadObjectComboBox_step1()
        {
            messageTab.ImageIndex = -1;
            objectReloadButton.Visible = false;
            objectLoadImage.Visible = true;
            objectComboBox.DataSource = null;
            objectComboBox.SelectedIndexChanged -=
                new System.EventHandler(this.loadAttributeComboBoxAsync);
        }

        private void loadObjectComboBox_step2(List<KeyValuePair<string, string>> objList)
        {
            objectComboBox.DataSource = new BindingSource(objList, null);
            objectComboBox.DisplayMember = "Value";
            objectComboBox.ValueMember = "Key";
            objectComboBox.SelectedValue = FimTools.OBJECT_PERSON;

            this.objectComboBox.SelectedIndexChanged +=
                new System.EventHandler(this.loadAttributeComboBoxAsync);
        }

        private void loadObjectComboBox_step3()
        {
            objectLoadImage.Visible = false;
            objectReloadButton.Visible = true;
        }

        private void setObjectType(string value)
        {
            try
            {
                objectComboBox.SelectedIndexChanged -=
                    new System.EventHandler(this.loadAttributeComboBoxAsync);

                objectComboBox.SelectedValue = value;
                loadAttributeComboBox();

                this.objectComboBox.SelectedIndexChanged +=
                    new System.EventHandler(this.loadAttributeComboBoxAsync);
            }
            catch (Exception exc)
            {
                log.Error("[setObjectType]", exc);
                showErrorIcon();
            }
        }

        private void loadAttributeComboBoxAsync(object sender, EventArgs e)
        {
            //// Create the delegate.
            //AsyncVoidMethodBool caller = new AsyncVoidMethodBool(loadAttributeComboBox);
            //// Initiate the asychronous call.
            //attributeThread = caller.BeginInvoke(false, null, null);
            attributeThread = new Thread(new ThreadStart(loadAttributeComboBox));
            attributeThread.Start();
        }

        private void reloadAttributeComboBoxAsync(object sender, EventArgs e)
        {
            //// Create the delegate.
            //AsyncVoidMethodBool caller = new AsyncVoidMethodBool(loadAttributeComboBox);
            //// Initiate the asychronous call.
            //attributeThread = caller.BeginInvoke(true, null, null);
            attributeThread = new Thread(new ThreadStart(reloadAttributeComboBox));
            attributeThread.Start();
        }

        private void loadAttributeComboBox()
        {
            loadAttributeComboBox(false);
        }

        private void reloadAttributeComboBox()
        {
            loadAttributeComboBox(true);
        }

        private void loadAttributeComboBox(bool refresh)
        {
            object objectName = null;
            List<KeyValuePair<string, string>> attrList = null;
            
            // Clear the contents of the current selection.
            currentAttributeSelection.Clear();

            if (InvokeRequired)
            {
                // 'Invoke' is necessary to access cross-thread controls.
                Invoke(new MethodInvoker(
                    delegate
                    {
                        objectName = loadAttributeComboBox_step1();
                    }));
            }
            else
            {
                objectName = loadAttributeComboBox_step1();
            }

            // Do not include this block in the Invoke call!
            if (objectName != null)
            {
                if (refresh)
                {
                    tools.RefreshAttributeDictionary(objectName.ToString());
                }
                var attrDict = tools.GetAttributeDictionary(objectName.ToString());
                attrList = EnsynchTools.SortDictionaryByValue(attrDict);
            }

            if (InvokeRequired)
            {
                Invoke(new MethodInvoker(
                    delegate
                    {
                        loadAttributeComboBox_step2(attrList);
                    }));
            }
            else
            {
                loadAttributeComboBox_step2(attrList);
            }
        }

        private object loadAttributeComboBox_step1()
        {
            object objectName;
            messageTab.ImageIndex = -1;
            attributeReloadButton.Visible = false;
            attributeLoadImage.Visible = true;
            attributeCheckedListBox.DataSource = null;
            objectName = objectComboBox.SelectedValue;
            if (objectName != null)
            {
                if (string.IsNullOrEmpty(xpathTextBox.Text))
                {
                    xpathTextBox.Text = "/" + objectName;
                }
            }
            allCheckBox.Checked = false;
            return objectName;
        }

        private void loadAttributeComboBox_step2(List<KeyValuePair<string, string>> attrList)
        {
            if (attrList != null)
            {
                attributeCheckedListBox.DataSource = new BindingSource(attrList, null);
            }
            attributeCheckedListBox.DisplayMember = "Value";
            attributeCheckedListBox.ValueMember = "Key";

            attributeLoadImage.Visible = false;
            attributeReloadButton.Visible = true;
        }

        private void allCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            setCheckedBypassEvents_allCheckBox(allCheckBox.Checked);
            syncQueryAttributesAsync();
        }

        private void setCheckedBypassEvents_allCheckBox(bool check)
        {
            for (int i = 0; i < attributeCheckedListBox.Items.Count; i++)
            {
                attributeCheckedListBox.ItemCheck -= new ItemCheckEventHandler(attributeCheckedListBox_ItemCheck);
                attributeCheckedListBox.SetItemChecked(i, check);
                attributeCheckedListBox.ItemCheck += new ItemCheckEventHandler(attributeCheckedListBox_ItemCheck);
            }
        }

        private void attributeCheckedListBox_ItemCheck(object sender, ItemCheckEventArgs e)
        {
            //var item = (KeyValuePair<string, string>)attributeCheckedListBox.Items[e.Index];
            //log.DebugFormat("You clicked {0} ({1})", item.Value, item.Key);
            if (allCheckBox.Checked)
            {
                setCheckedBypassEvents_attributeCheckedListBox(false);
            }
            syncQueryAttributesAsync();
        }

        private void setCheckedBypassEvents_attributeCheckedListBox(bool check)
        {
            allCheckBox.CheckedChanged -= new EventHandler(allCheckBox_CheckedChanged);
            allCheckBox.Checked = check;
            allCheckBox.CheckedChanged += new EventHandler(allCheckBox_CheckedChanged);
        }

        private void syncQueryAttributesAsync()
        {
            queryThread = new Thread(new ThreadStart(syncQueryAttributes));
            queryThread.Start();
        }

        private void syncQueryAttributes()
        {
            try
            {
                string xpath = null;
                Dictionary<string, string> additions = new Dictionary<string, string>();
                Dictionary<string, string> subtractions = new Dictionary<string, string>();
                bool showDisplayNames = false;

                // 'Invoke' is necessary to access cross-thread controls.
                Invoke(new MethodInvoker(
                    delegate
                    {
                        runQueryImage.Visible = true;
                        xpath = xpathTextBox.Text;

                        // Update the list of currently selected attributes.
                        for (int i = 0; i < attributeCheckedListBox.Items.Count; i++)
                        {
                            object obj = attributeCheckedListBox.Items[i];
                            var item = (KeyValuePair<string, string>)obj;
                            if (attributeCheckedListBox.GetItemChecked(i))
                            {
                                if (!currentAttributeSelection.ContainsKey(item.Key))
                                {
                                    additions.Add(item.Key, "[" + item.Value + "]");
                                    currentAttributeSelection.Add(item.Key, "[" + item.Value + "]");
                                }
                            }
                            else
                            {
                                if (currentAttributeSelection.ContainsKey(item.Key))
                                {
                                    subtractions.Add(item.Key, "[" + item.Value + "]");
                                    currentAttributeSelection.Remove(item.Key);
                                }
                            }
                        }

                        // Maybe toggle display names.
                        string format = referenceFormatComboBox.SelectedValue.ToString();
                        showDisplayNames = FimTools.MustDereferenceGuids(format);
                    }));

                // Do not include this block in the Invoke call!
                FimQuery fimQuery = new FimQuery(xpath, tools);
                if (showDisplayNames)
                {
                    fimQuery.AddAttributes(additions);
                    fimQuery.MaybeToggleDisplayNames(additions.Keys.AsEnumerable(), true);
                    fimQuery.MaybeToggleDisplayNames(subtractions.Keys.AsEnumerable(), false);
                    fimQuery.RemoveAttributes(subtractions, true);
                }
                else
                {
                    fimQuery.AddAttributes(additions);
                    fimQuery.RemoveAttributes(subtractions, true);
                }

                // 'Invoke' is necessary to access cross-thread controls.
                Invoke(new MethodInvoker(
                    delegate
                    {
                        xpathTextBox.Text = fimQuery.ToString();
                    }));
            }
            catch (FaultException exc)
            {
                handleFaultException(exc);
            }
            catch (Exception exc)
            {
                log.Error("[syncQueryAttributes]", exc);
                showErrorIcon();
            }
            finally
            {
                // 'Invoke' is necessary to access cross-thread controls.
                Invoke(new MethodInvoker(
                    delegate
                    {
                        runQueryImage.Visible = theQueryState == QueryState.RUNNING;
                    }));
            }
        }

        private void handleFaultException(FaultException exc)
        {
            try
            {
                // The exception message may contain too much info.
                // Try to extract the message.
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(exc.Message.ToString());
                XmlNamespaceManager nsMgr = new XmlNamespaceManager(doc.NameTable);
                XmlNode faultReasonNode = doc.SelectSingleNode(
                    "//s:Fault/s:Reason/s:Text", nsMgr);

                StringBuilder buffer = new StringBuilder();
                buffer.AppendLine(exc.GetType().ToString());
                buffer.AppendLine();
                if (faultReasonNode != null)
                {
                    buffer.AppendLine(faultReasonNode.InnerText);
                }
                else
                {
                    buffer.AppendLine(exc.Message);
                }
                buffer.AppendLine();
                buffer.AppendLine(exc.StackTrace);
                buffer.AppendLine();
                log.Error(buffer.ToString());
                showErrorIcon();
            }
            catch
            {
                log.Error(string.Format("[{0}]", EnsynchTools.WhichMethodCalledMe().Name), exc);
                showErrorIcon();
            }
        }

        private void runQueryAsync(object sender, EventArgs e)
        {
            queryThread = new Thread(new ThreadStart(runQuery));
            queryThread.Start();
        }

        private void runQuery()
        {
            DateTime start = DateTime.Now;
            try
            {
                DataTable table = new DataTable();
                //Dictionary<string, string> selectedAttributes =
                //    new Dictionary<string, string>();
                string xpath = null;
                bool dereference = false;
                string format = null;
                string objectName = null;
                string whereAmI = EnsynchTools.WhereAmI().Name;

                // 'Invoke' is necessary to access cross-thread controls.
                Invoke(new MethodInvoker(
                    delegate
                    {
                        TheQueryState = QueryState.RUNNING;
                        xmlTextBox.Text = "";
                        messageTab.ImageIndex = -1;
                        dataGridView1.DataSource = null;
                        xpath = xpathTextBox.Text;
                        log.Info(whereAmI + ": " + xpath);
                        //selectedAttributes.Add(ATTRIBUTE_DISPLAY_NAME, "Display Name");
                        //table.Columns.Add("Display Name");
                        //selectedAttributes.Add(ATTRIBUTE_OBJECT_ID, "Object ID");
                        //table.Columns.Add("Object ID");
                        //for (int i = 0; i < attributeCheckedListBox.Items.Count; i++)
                        //{
                        //    if (attributeCheckedListBox.GetItemChecked(i))
                        //    {
                        //        object obj = attributeCheckedListBox.Items[i];
                        //        var item = (KeyValuePair<string, string>)obj;
                        //        if (!selectedAttributes.ContainsKey(item.Key))
                        //        {
                        //            selectedAttributes.Add(item.Key, item.Value);
                        //            table.Columns.Add(item.Value);
                        //        }
                        //    }
                        //}
                        //dataGridView1.DataSource = table;
                        format = referenceFormatComboBox.SelectedValue.ToString();
                        //objectName = objectComboBox.SelectedValue.ToString();
                    }));

                // Do not include this block in the Invoke call!
                FimQuery fimQuery = new FimQuery(xpath, tools);
                objectName = fimQuery.ObjectType;
                FimQueryResult result = fimQuery.GetResult(this);
                // Call this asynchronously so that it doesn't freeze the interface.
                result.CreateTable();

                // 'Invoke' is necessary to access cross-thread controls.
                Invoke(new MethodInvoker(
                    delegate
                    {
                        dataGridView1.DataSource = result.DataView;
                    }));

                result.ReadAll();
            }
            catch (FaultException exc)
            {
                handleFaultException(exc);
            }
            catch (ThreadAbortException exc)
            {
                log.Info("Query aborted");
            }
            catch (Exception exc)
            {
                log.Error("[runQuery]", exc);
                showErrorIcon();
            }
            finally
            {
                TheQueryState = QueryState.STOPPED;
                DateTime end = DateTime.Now;
                TimeSpan elapsed = new TimeSpan(end.Ticks - start.Ticks);
                log.Info("Query finished.\r\nElapsed time: " + elapsed.ToString());
            }
        }

        private object printAttribute(RmResource obj, string attribute,
            Dictionary<string, RmFactory.RmAttributeType> attrTypeDict,
            bool dereference, string objectName, string format)
        {
            StringBuilder buffer = new StringBuilder();

            if (!obj.ContainsKey(new RmAttributeName(attribute)) || obj[attribute] == null)
            {
                return null;
            }
            
            if (obj[attribute].IsMultiValue)
            {
                bool first = true;
                foreach (var value in obj[attribute].Values)
                {
                    if (!first)
                    {
                        buffer.Append("; ");
                    }

                    if (attrTypeDict[attribute] == RmFactory.RmAttributeType.Reference)
                    {
                        buffer.Append(tools.PrintReference(
                            value.ToString(),
                            format));
                    }
                    else
                    {
                        buffer.Append(value.ToString());
                    }

                    first = false;
                }
            }
            else if (obj[attribute].Value == null)
            {
                return null;
            }
            else if (attribute.Equals(FimTools.ATTRIBUTE_OBJECT_ID))
            {
                return tools.PrintReference(
                    obj[attribute].Value.ToString(),
                    FimTools.FORMAT_GUID);
            }
            else if (attrTypeDict[attribute].Equals(FimTools.TYPE_REFERENCE))
            {
                return tools.PrintReference(
                    obj[attribute].Value.ToString(),
                    format);
            }
            else
            {
                return obj[attribute].Value.ToString();
            }

            return buffer.ToString();            
        }

        private void focusMessageTab()
        {
            if (InvokeRequired)
            {
                // 'Invoke' is necessary to access cross-thread controls.
                Invoke(new MethodInvoker(
                    delegate
                    {
                        this.tabContainer.SelectedTab = this.messageTab;
                    }));
            }
            else
            {
                this.tabContainer.SelectedTab = this.messageTab;
            }
        }

        private void showErrorIcon()
        {
            if (InvokeRequired)
            {
                // 'Invoke' is necessary to access cross-thread controls.
                Invoke(new MethodInvoker(
                    delegate
                    {
                        messageTab.ImageIndex = 0;
                    }));
            }
            else
            {
                messageTab.ImageIndex = 0;
            }
        }

        private void stopQuery(object sender, EventArgs e)
        {
            if (queryThread != null)
            {
                if (queryThread.ThreadState == System.Threading.ThreadState.Suspended)
                {
                    // I know, this method is deprecated. It's just so darn handy.
                    queryThread.Resume();
                }
                queryThread.Abort();
            }
        }

        #region ToolTip on CheckedListBox

        private int ttIndex;
        private void ShowToolTip()
        {
            ttIndex = attributeCheckedListBox.IndexFromPoint(
                attributeCheckedListBox.PointToClient(MousePosition));
            if (ttIndex > -1)
            {
                Point p = PointToClient(MousePosition);
                checkBoxToolTip.SetToolTip(attributeCheckedListBox,
                    ((KeyValuePair<string, string>)attributeCheckedListBox.Items[ttIndex]).Key);
            }
        }

        private void attributeCheckedListBox_MouseMove(object sender, MouseEventArgs e)
        {
            //Make ttIndex a global integer variable to store index of item currently showing tooltip.
            //Check if current location is different from item having tooltip if so call method
            if (ttIndex != attributeCheckedListBox.IndexFromPoint(e.Location))
                ShowToolTip();
        }

        #endregion ToolTip on CheckedListBox

        private void referenceFormatComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            string format = referenceFormatComboBox.SelectedValue.ToString();
            referenceImage.Visible = FimTools.MustDereferenceGuids(format);
            toggleDisplayNamesAsync();
        }

        private void toggleDisplayNamesAsync()
        {
            queryThread = new Thread(new ThreadStart(toggleDisplayNames));
            queryThread.Start();
        }

        private void toggleDisplayNames()
        {
            try
            {
                string xpath = null;
                bool showDisplayNames = false;
                // 'Invoke' is necessary to access cross-thread controls.
                Invoke(new MethodInvoker(
                    delegate
                    {
                        runQueryImage.Visible = true;
                        xpath = xpathTextBox.Text;
                        string format = referenceFormatComboBox.SelectedValue.ToString();
                        showDisplayNames = FimTools.MustDereferenceGuids(format);
                    }));

                // Do not include this block in the Invoke call!
                FimQuery fimQuery = new FimQuery(xpath, tools);
                fimQuery.ToggleDisplayNames(showDisplayNames);

                // 'Invoke' is necessary to access cross-thread controls.
                Invoke(new MethodInvoker(
                    delegate
                    {
                        xpathTextBox.Text = fimQuery.ToString();
                    }));
            }
            catch (FaultException exc)
            {
                handleFaultException(exc);
            }
            catch (Exception exc)
            {
                log.Error("[toggleDisplayNames]", exc);
                showErrorIcon();
            }
            finally
            {
                // 'Invoke' is necessary to access cross-thread controls.
                Invoke(new MethodInvoker(
                    delegate
                    {
                        runQueryImage.Visible = theQueryState == QueryState.RUNNING;
                    }));
            }
        }

        private void pauseQueryButton_Click(object sender, EventArgs e)
        {
            if (queryThread != null)
            {
                // I know, this method is deprecated. It's just so darn handy.
                queryThread.Suspend();
                TheQueryState = QueryState.PAUSED;
            }
        }

        private void resumeQueryButton_Click(object sender, EventArgs e)
        {
            if (queryThread != null)
            {
                // I know, this method is deprecated. It's just so darn handy.
                queryThread.Resume();
                TheQueryState = QueryState.RUNNING;
            }
        }

        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            AboutBox aboutBox = new AboutBox();
            aboutBox.ShowDialog(this);
        }

        private void resetQueryButton_Click(object sender, EventArgs e)
        {
            object objectName = objectComboBox.SelectedValue;
            if (objectName != null)
            {
                xpathTextBox.Text = "/" + objectName;
            }
            currentAttributeSelection.Clear();
            syncQueryAttributesAsync();
        }

        private void dataGridView1_DataError(object sender, DataGridViewDataErrorEventArgs e)
        {
            // Blank to supress errors with binary data.
        }

        private void copyResultsAsCSVToolStripMenuItem_Click(object sender, EventArgs e)
        {
            DataGridViewColumnCollection columns = dataGridView1.Columns;
            if (columns.Count <= 0)
            {
                return;
            }
            StringBuilder buffer = new StringBuilder();
            for (int j = 0; j < dataGridView1.Columns.Count; j++)
            {
                if (j > 0)
                {
                    buffer.Append(",");
                }
                buffer.Append("\"" + string.Format("{0}", columns[j].HeaderText).Replace("\"", "\"\"") + "\"");
            }
            buffer.AppendLine();
            for (int i = 0; i < dataGridView1.RowCount; i++)
            {
                for (int j = 0; j < dataGridView1.Rows[i].Cells.Count; j++)
                {
                    if (j > 0)
                    {
                        buffer.Append(",");
                    }
                    buffer.Append("\"" + string.Format("{0}", dataGridView1.Rows[i].Cells[j].Value).Replace("\"", "\"\"") + "\"");
                }
                buffer.AppendLine();
            }
            Clipboard.SetDataObject(buffer.ToString());
        }

        /// <summary>
        /// Gavin Jin - MSFT
        /// http://social.msdn.microsoft.com/forums/en-US/winformsdatacontrols/thread/e64f035b-77a6-40c8-ba59-7d80533235f3/
        /// </summary>
        private void dataGridView1_RowPostPaint(object sender, DataGridViewRowPostPaintEventArgs e)
        {
            using (SolidBrush b = new SolidBrush(dataGridView1.RowHeadersDefaultCellStyle.ForeColor))
            {
                e.Graphics.DrawString((e.RowIndex + 1).ToString(System.Globalization.CultureInfo.CurrentUICulture),
                    dataGridView1.DefaultCellStyle.Font, b, e.RowBounds.Location.X + 20, e.RowBounds.Location.Y + 4);
            }
        }

        private UIData getUIData()
        {
            UIData data = new UIData();
            data.ObjectType = objectComboBox.SelectedValue.ToString();
            data.FilterAttributesAll = allCheckBox.Checked;
            foreach (object obj in attributeCheckedListBox.CheckedItems)
            {
                var item = (KeyValuePair<string, string>)obj;
                data.FilterAttributes.Add(item.Key);
            }
            data.ReferenceFormat = referenceFormatComboBox.SelectedValue.ToString();
            data.Query = xpathTextBox.Text;
            return data;
        }

        private void loadUIData(UIData data)
        {
            //objectThread = null;
            //attributeThread = null;
            //objectComboBox.SelectedValue = data.ObjectType;
            //DateTime startTime = DateTime.Now;
            //TimeSpan elapsed = new TimeSpan(0);
            //TimeSpan maxWait = new TimeSpan(0, 0, 2);
            //while (attributeThread == null)
            //{
            //    elapsed = DateTime.Now - startTime;
            //    if (elapsed > maxWait)
            //    {
            //        break;
            //    }
            //}
            //log.Debug("[loadUIData] waited " + elapsed.TotalMilliseconds);
            ////if (objectThread != null && !objectThread.IsCompleted)
            ////{
            ////    objectThread.AsyncWaitHandle.WaitOne(1000);
            ////}
            //if (attributeThread != null)
            //{
            //    attributeThread.Join(1000);
            //}

            setObjectType(data.ObjectType);
            allCheckBox.Checked = data.FilterAttributesAll;
            if (!data.FilterAttributesAll)
            {
                attributeCheckedListBox.ClearSelected();
                for (int i = 0; i < attributeCheckedListBox.Items.Count; i++)
                {
                    object obj = attributeCheckedListBox.Items[i];
                    var item = (KeyValuePair<string, string>)obj;
                    if (data.FilterAttributes.Contains(item.Key))
                    {
                        attributeCheckedListBox.SetItemChecked(i, true);
                    }
                }
            }
            referenceFormatComboBox.SelectedValue = data.ReferenceFormat;
            xpathTextBox.Text = data.Query;
        }

        private void saveButton_Click(object sender, EventArgs e)
        {
            save();
        }

        private void save()
        {
            if (!string.IsNullOrEmpty(currentFilePath))
            {
                save(currentFilePath);
            }
            else
            {
                saveAs();
            }
        }

        private void save(string filePath)
        {
            try
            {
                UIData data = getUIData();
                using (StreamWriter outfile = new StreamWriter(filePath))
                {
                    outfile.Write(data.ToXml());
                }
                this.currentFilePath = filePath;
            }
            catch (Exception exc)
            {
                log.Error("[saveButton_Click]", exc);
                showErrorIcon();
            }
        }

        private void saveAsButton_Click(object sender, EventArgs e)
        {
            saveAs();
        }

        private void saveAs()
        {
            if (this.saveFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                save(this.saveFileDialog1.FileName);
            }
        }

        private void openButton_Click(object sender, EventArgs e)
        {
            open();
        }

        private void open()
        {
            if (this.openFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                open(this.openFileDialog1.FileName);
            }
        }

        private void open(string filePath)
        {
            UIData data = UIData.FromXml(filePath);
            loadUIData(data);
            this.currentFilePath = filePath;
        }

        private void recentMenuItem_Click(object sender, EventArgs e)
        {
            ToolStripMenuItem recentMenuItem = (ToolStripMenuItem)sender;
            string key = recentMenuItem.Text;
            string value = recent[key];
            this.open(value);
        }

        void recent_ListChanged(object sender, EventArgs e)
        {
            if (this.InvokeRequired)
            {
                Invoke(new MethodInvoker(
                    delegate
                    {
                        recent_ListChanged();
                    }));
            }
            else
            {
                recent_ListChanged();
            }
        }

        private void recent_ListChanged()
        {
            this.recentToolStripMenuItem.DropDownItems.Clear();
            for (int i = recent.Count - 1; i >= 0; i--)
            {
                var item = recent[i];
                ToolStripMenuItem recentMenuItem;
                recentMenuItem = new System.Windows.Forms.ToolStripMenuItem();
                this.recentToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
                            recentMenuItem});
                // 
                // asdfToolStripMenuItem
                // 
                recentMenuItem.Name = "recentMenuItem" + i;
                //recentMenuItem.Size = new System.Drawing.Size(152, 22);
                recentMenuItem.Text = item.Key;
                recentMenuItem.ToolTipText = item.Value;
                recentMenuItem.Click += new System.EventHandler(this.recentMenuItem_Click);
            }
        }

        private void newButton_Click(object sender, EventArgs e)
        {
            setCheckedBypassEvents_allCheckBox(false);
            setCheckedBypassEvents_attributeCheckedListBox(false);
            currentFilePath = null;
            resetQueryButton_Click(null, null);
        }

    }
}
